iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Software Development

Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程系列 第 10

Day10 - 重試之前先判斷:錯誤分類與處理路徑設計

  • 分享至 

  • xImage
  •  

在分散式系統中,網路的不確定性、第三方服務的不可預測、跨服務間的資源競爭,都讓錯誤更頻繁、更難排查;
在編排架構下,以分類並結構化的方式處理錯誤,讓開發者專注於業務決策。

1. 錯誤的分類(Error Taxonomy)

  • Transient Errors(暫時性錯誤):網路抖動、暫時的逾時(Timeout)、短暫服務不可用。
  • Permanent Errors(永久性錯誤):輸入不合法、業務規則不符(例如卡號錯誤)。
  • Business Failures(業務失敗):流程的合理結果,如庫存不足、風險控管拒絕;不是 Bug。
  • System Failures(系統崩潰):Worker crash、節點故障、基礎設施層故障。

下表為常見錯誤類型與對應策略:

類型 常見情境 是否適合重試 建議策略
Transient 短暫網路錯誤、第三方 5xx、逾時 可考慮自動重試
Permanent 參數驗證失敗、資源不存在、卡號無效 快速失敗並回報呼叫端
Business Failure 庫存不足、額度不足、黑名單 視情況 分支流程、人工介入或補償
System Failure Worker crash、部署重新啟動 不需要手動 交由平台事件重播與任務續跑復原

2. Temporal 的錯誤處理

2.1 Activity Error Handling

  • 錯誤分類驅動:暫時性→重試;永久性→不重試;業務失敗→拋出由 Workflow 層處理。
  • 長任務治理:以心跳與進度 checkpoint 加上取消機制,避免重跑整批與資源遺漏。

2.2 Workflow Error Handling

  • 可重播(Deterministic Replay):Worker crash 不會遺失狀態,依 Event History 還原。
  • 錯誤處理:捕捉 Activity / Child Workflow 錯誤,做補償或分支。

2.3 處理策略分層

  1. Activity 層:封裝外部呼叫與資料操作,設計重試與冪等性、加入 Heartbeat 與取消機制。
  2. Workflow 層:流程決策、補償策略(例如 Saga)、與人工介入路徑。
  3. 系統層:避免自建脆弱的重試/排程,交由平台處理。
  4. 業務層:錯誤指標與警示、待處理清單、SLA/客戶溝通。

3. 反模式(Anti-Patterns)

  • 所有錯誤都重試:會重複副作用(重複寄信、重複扣款),導致更大影響。
  • 在 Activity 裡 try-catch 全部錯誤並吞掉:Temporal 無法判斷是否應重試或標記失敗,反而降低可靠性。
  • 無限重試或沒有退避:耗盡資源、放大雪崩效應。
  • 沒有補償邏輯:遇到真正的業務失敗(非暫時性)時完全無解,只會一直重試。

4. 案例:Email 寄送

  • Activity
public interface EmailActivities {
    void sendEmail(String messageId, String to, String subject, String body);
}

public class EmailActivitiesImpl implements EmailActivities {
    private final EmailClient client;            // 外部 SMTP/API 客戶端
    private final SentRepository sentRepository; // 紀錄已寄出的 messageId(冪等)

    public EmailActivitiesImpl(EmailClient client, SentRepository sentRepository) {
        this.client = client;
        this.sentRepository = sentRepository;
    }

    @Override
    public void sendEmail(String messageId, String to, String subject, String body) {
        if (sentRepository.exists(messageId)) {
            return; // 已寄出則直接返回(冪等)
        }
        if (!EmailValidator.isValid(to)) {
            // 永久性錯誤:不重試,使用型別化的 ApplicationFailure
            throw ApplicationFailure.newNonRetryableFailure("invalid recipient", "InvalidRecipient");
        }
        try {
            client.send(to, subject, body); // 可能拋出暫時性錯誤(例如網路問題)
            sentRepository.save(messageId);
        } catch (IOException e) {
            // 暫時性錯誤:記錄內部細節,對外拋出型別化錯誤(附安全細節)
            Activity.getExecutionContext().getLogger()
                    .error("sendEmail transient failure: messageId={}, to={}", messageId, to, e);
            throw ApplicationFailure.newFailure("transient email failure", "TransientEmail", messageId, to);
        }
    }
}
  • Workflow:捕捉 ActivityFailure → 取出 ApplicationFailure.type 分支(例如 InvalidRecipient 永久性、TransientEmail 暫時性),決定補償或替代路徑。
  • 若 Activity 有設定重試,會先於活動端重試;當重試耗盡(或未設定重試)時,才會在 Workflow 收到 ActivityFailure 進入下方 catch。
try {
  email.sendEmail(messageId, to, subject, body);
} catch (ActivityFailure af) {
  Throwable cause = af.getCause();
  if (!(cause instanceof ApplicationFailure)) {
    throw af; // 非應用層錯誤:直接拋出
  }
  ApplicationFailure app = (ApplicationFailure) cause;
  switch (app.getType()) {
    case "InvalidRecipient":
      // 永久性:Workflow 決策改走人工/替代路徑
      return;
    case "TransientEmail":
      // 暫時性:Workflow 決策稍後再試或切換方案(具體策略下篇)
      return;
    default:
      throw af; // 未識別:讓 Workflow 失敗由上層處理
  }
}
  • Worker:無需額外處理,讓 ApplicationFailure 由 SDK 上報;可於 Activity/Workflow 加上 log/metrics 以利觀測。

5. 實務建議

  • 錯誤分類先行:先判斷暫時性/永久性/業務失敗,避免盲目重試。
  • Retry Policy 顯式化:原則先行(細節與參數配置將於下一篇展開)。
  • 冪等性設計:以業務鍵(如 messageId、orderId)去重,避免重複副作用。
  • 可觀測性:蒐集錯誤率、嘗試次數、逾時、取消、補償次數等指標,持續調整策略。
  • 人機協作:對需要人工決策的錯誤提供 Signal/Update 入口與待處理清單。

結語

本篇整理了分類、機制與分層,讓錯誤處理成為一級公民並降低複雜度;

下一篇將深入 Retry Policy 與 Timeout Policy 的參數設計與實務。


上一篇
Day09 - 不管是一次、一百次還是一萬次,我還是想跟你說我愛...冪等性
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言